#define DDCLLUA_CORE

#include "ddclsocket.h"
#include "ddclerr.h"
#include "ddcllog.h"

#include "lua-ddclservice.h"

#include <stdio.h>
#include <stdlib.h>

static int
l_bind (lua_State * L){
    const char * host = luaL_checkstring(L, 1);
    dduint16 port = (dduint16)luaL_checkinteger(L, 2);
    int protocol = DDCL_SOCKET_TCP;
    int family = DDCL_SOCKET_IPV0;

    if(lua_gettop(L) >= 3){
        protocol = luaL_checkinteger(L, 3);
    }
    if(lua_gettop(L) >= 4){
        family = luaL_checkinteger(L, 4);
    }
    ddcl_Socket fd = ddcl_bind_socket(host, port, protocol, family);
    if(fd == 0){
        return luaL_error(L, "bind address error");
    }
    lua_pushinteger(L, fd);
    return 1;
}

static int
l_connect (lua_State * L){
    LDDCL_FIND_CTX;
    const char * host = luaL_checkstring(L, 1);
    dduint16 port = (dduint16)luaL_checkinteger(L, 2);
    ddcl_Session session;
    int err = ddcl_new_session(ctx->svr, &session);
    if(err){
        return luaL_error(L, ddcl_err(err));
    }

    ddcl_Socket fd = ddcl_connect_socket(ctx->svr, host, port, session);
    if(fd == 0){
        return luaL_error(L, "can not connect to %s:%d", host, port);
    }
    return lddcl_yield_for_session(L, ctx, session);
}

static int
l_listen (lua_State * L){
    LDDCL_FIND_CTX;

    ddcl_Socket fd = (ddcl_Socket)luaL_checkinteger(L, 1);
    int backlog = 1000;
    if(lua_gettop(L) >= 2){
        backlog = (int)luaL_checkinteger(L, 2);
    }
    ddcl_Session session;
    int err = ddcl_listen_socket(ctx->svr, &session, fd, backlog);
    if(err){
        return luaL_error(L, ddcl_err(err));
    }
    return lddcl_yield_for_session(L, ctx, session);
}

static int
l_accept (lua_State * L){
    LDDCL_FIND_CTX;
    ddcl_Socket fd = (ddcl_Socket)luaL_checkinteger(L, 1);

    ddcl_Session session;
    int err = ddcl_accept_socket(fd, ctx->svr, &session);
    if(err){
        return luaL_error(L, ddcl_err(err));
    }
    return lddcl_yield_for_session(L, ctx, session);
}

static int
l_read (lua_State * L){
    LDDCL_FIND_CTX;

    ddcl_Socket fd = (ddcl_Socket)luaL_checkinteger(L, 1);
    size_t sz = 0;
    if(lua_gettop(L) >= 2){
        sz = luaL_checkinteger(L, 2);
    }
    ddcl_Session session;
    int err = ddcl_read_socket(fd, ctx->svr, sz, &session);
    if(err){
        return luaL_error(L, ddcl_err(err));
    }
    return lddcl_yield_for_session(L, ctx, session);
}

static int
l_send (lua_State * L){
    ddcl_Socket fd = (ddcl_Socket)luaL_checkinteger(L, 1);
    size_t sz;
    const char * data = luaL_checklstring(L, 2, &sz);
    int err = ddcl_send_socket(fd, 0, data, sz);
    if(err){
        return luaL_error(L, "%d: %s", fd, ddcl_err(err));
    }
    return 0;
}

static int
l_sendraw (lua_State * L){
    ddcl_Socket fd = (ddcl_Socket)luaL_checkinteger(L, 1);
    luaL_checktype(L, 2, LUA_TLIGHTUSERDATA);
    size_t sz = luaL_checkinteger(L, 3);
    const char * data = (const char *)lua_touserdata(L, 2);
    int err = ddcl_send_socket(fd, 0, data, sz);
    if(err){
        return luaL_error(L, "%d: %s", fd, ddcl_err(err));
    }
    return 0;

}

static int
l_close (lua_State * L){
    ddcl_Socket fd = (ddcl_Socket)luaL_checkinteger(L, 1);
    int err = ddcl_close_socket(fd, 0);
    if(err){
        return luaL_error(L, ddcl_err(err));
    }
    return 0;
}

static int
l_getall_count (lua_State * L){
    lua_pushinteger(L, ddcl_getall_socket_count());
    return 1;
}

static int
l_forward (lua_State * L){
    LDDCL_FIND_CTX;
    ddcl_Socket fd = (ddcl_Socket)luaL_checkinteger(L, 1);
    luaL_checktype(L,  2, LUA_TFUNCTION);
    ddcl_Session session;
    int err = ddcl_forward_socket(fd, ctx->svr, &session);
    if(err){
        return luaL_error(L, ddcl_err(err));
    }
    lua_rawgetp(L, LUA_REGISTRYINDEX, &ctx->session_map);
    lua_pushinteger(L, session);
    lua_pushvalue(L, 2);
    lua_rawset(L, -3);
    lua_pop(L, 1);
    return 0;
}

static luaL_Reg _REG[] = {
    { NULL, NULL },
};

DDCLLUA int
luaopen_ddcl_socket (lua_State * L){
    luaL_checkversion(L);
    DDLUA_NEWLIB(L, "ddcl.socket", _REG);
    luapush_ddcl_socket(L, -1);
    return 1;
}

DDCLLUA int
luapush_ddcl_socket (lua_State * L, int i){
    lua_checkstack(L, 3);
    lua_pushvalue(L, i);
    DDLUA_PUSHFUNC(L, "bind", l_bind);
    DDLUA_PUSHFUNC(L, "connect", l_connect);
    DDLUA_PUSHFUNC(L, "listen", l_listen);
    DDLUA_PUSHFUNC(L, "accept", l_accept);
    DDLUA_PUSHFUNC(L, "read", l_read);
    DDLUA_PUSHFUNC(L, "send", l_send);
    DDLUA_PUSHFUNC(L, "sendraw", l_sendraw);
    DDLUA_PUSHFUNC(L, "close", l_close);
    DDLUA_PUSHFUNC(L, "getall_count", l_getall_count);
    DDLUA_PUSHFUNC(L, "forward", l_forward);

    DDLUA_PUSHENUM(L, "SOCKET_ACCEPT", DDCL_SOCKET_ACCEPT);
    DDLUA_PUSHENUM(L, "SOCKET_READ", DDCL_SOCKET_READ);
    DDLUA_PUSHENUM(L, "SOCKET_ERROR", DDCL_SOCKET_ERROR);

    DDLUA_PUSHENUM(L, "SOCKET_IPV0", DDCL_SOCKET_IPV0);
    DDLUA_PUSHENUM(L, "SOCKET_IPV4", DDCL_SOCKET_IPV4);
    DDLUA_PUSHENUM(L, "SOCKET_IPV6", DDCL_SOCKET_IPV6);
    DDLUA_PUSHENUM(L, "SOCKET_UDP", DDCL_SOCKET_UDP);
    DDLUA_PUSHENUM(L, "SOCKET_TCP", DDCL_SOCKET_TCP);
    lua_pop(L, 1);
    return 0;
}
